package com.jeeplus.modules.web.wx;

import com.jeeplus.common.config.Global;
import com.jeeplus.common.json.AjaxJson;
import com.jeeplus.common.utils.StringUtils;
import com.jeeplus.common.utils.audio.VideoUtil;
import com.jeeplus.common.utils.concurrent.threadpool.MyThreadPoolExecutor;
import com.jeeplus.common.utils.jpush.JpushClientUtil;
import com.jeeplus.common.utils.redis.RedisUtil;
import com.jeeplus.core.web.BaseController;
import com.jeeplus.modules.web.entity.*;
import com.jeeplus.modules.web.response.JPushResponse;
import com.jeeplus.modules.web.service.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author liangbing
 * @version 1.0
 * @date 2021/3/24 14:52
 * @description xxx
 */
@Api(value = "YjRoomCallsController", description = "棋牌室房间呼叫控制器")
@Controller
@RequestMapping(value = "/wx/roomCalls")
public class YjRoomCallsController extends BaseController {
    private static final Logger log = LoggerFactory.getLogger(YjRoomCallsController.class);

    @Autowired
    private YjRoomCallsService yjRoomCallsService;

    @Autowired
    private YjMahjongHallService yjMahjongHallService;

    @Autowired
    private YjMahjongHallRoomService yjMahjongHallRoomService;

    @Autowired
    private YjMahjongHallWaitersService yjMahjongHallWaitersService;

    @Autowired
    private YjUserRoomQrcodeService yjUserRoomQrcodeService;

    @Autowired
    private YjRoomCallsPushLogService yjRoomCallsPushLogService;

    @Autowired
    private YjRoomCallsWaitersService yjRoomCallsWaitersService;

    @Autowired
    private RedisUtil redisUtil;

    private static final String RELATIVE_FILE_PATH = "RelativeFilePath";
    public static final String FILE_NAME = "FileName";
    private static final String DURATION = "Duration";

    private static final String CALL_PUSH_REDIS_PREFIX = "CallPush_";
    private static final int MAX_RETRY_TIMES = 3;

    /**
     * 呼叫服务生
     * @param request
     * @return
     */
    @ApiOperation(notes = "voiceUpload", httpMethod = "POST", value = "微信端呼叫服务生")
    @ResponseBody
    @RequestMapping("voiceUpload")
    public AjaxJson voiceUpload(HttpServletRequest request) {
        log.info("呼叫声音文件上传开始");

        AjaxJson j = new AjaxJson();

        // 0. 检查参数
        String openid = request.getParameter("openid");
        if (StringUtils.isBlank(openid)) {
            j.setSuccess(false);
            j.setMsg("用户不能为空！");
            return j;
        }

        // 检查当前用户是否已经绑定了有效的房间二维码，以及是否以及过期
        YjUserRoomQrcode yjUserRoomQrcode = yjUserRoomQrcodeService.getByOpenid(openid);
        if (yjUserRoomQrcode == null) {
            j.setSuccess(false);
            j.setMsg("用户未绑定房间二维码或者二维码已过期！");
            return j;
        }

        // 检查当前棋牌室是否开通了呼叫功能
        String hallRoomId = yjUserRoomQrcode.getMahjongHallRoom().getId();
        YjMahjongHallRoom yjMahjongHallRoom = yjMahjongHallRoomService.get(hallRoomId);
        String hallId = yjMahjongHallRoom.getMahjongHall().getId();
        YjMahjongHall yjMahjongHall = yjMahjongHallService.get(hallId);
        if (!new Integer(1).equals(yjMahjongHall.getIsOpenCall())) {
            j.setSuccess(false);
            j.setMsg("当前棋牌室未开通呼叫功能");
            return j;
        }

        // 设置hallId
        yjUserRoomQrcode.getMahjongHallRoom().setMahjongHall(new YjMahjongHall(hallId));

        // 1.获取从前台传过来的声音文件
        MultipartHttpServletRequest req = (MultipartHttpServletRequest) request;
        MultipartFile multipartFile = req.getFile("file");

        // 2. 保存文件
        Map<String, String> map = saveFile(multipartFile);
        if (!map.containsKey(FILE_NAME)) {
            j.setSuccess(false);
            j.setMsg("呼叫声音保存失败！");
            return j;
        }

        String filename = map.get(FILE_NAME);
        String duration = map.get(DURATION);

        // 3. 异步推送呼叫请求
        //String voicePath = Global.getConfig("domainUrl") + request.getContextPath() + relativeFilePath;
        String voicePath = Global.getConfig("wxUploadFilesUrl") + filename;
        pushCallRequest(yjUserRoomQrcode, voicePath, duration);

        // 返回
        j.setMsg("呼叫成功，请稍候！");
        // j.getBody().put("data", yjRoomCalls);
        return j;
    }

    /**
     * 保存文件
     * @param multipartFile
     * @return
     */
    private Map<String, String> saveFile(MultipartFile multipartFile) {

        Map<String, String> map = new ConcurrentHashMap<>();

        // 获得文件扩展名
        String extOfFile = getExtOfFile(multipartFile);
        // 3.保存到本地
        BufferedOutputStream bos = null;
        String filename = null;
        try {
            String wxFilesDirPath = Global.getConfig("wxUploadFiles");
            File dir = new File(wxFilesDirPath);
            if (!dir.exists()) {// 判断文件目录是否存在
                dir.mkdirs();
            }

            filename = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + (int) (Math.random() * 1000) + "." + extOfFile;

            Path filePath = Paths.get(wxFilesDirPath, filename);
            String destFilePath = filePath.toString();
            bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
            bos.write(multipartFile.getBytes());

            long duration = VideoUtil.getDuration(destFilePath);

            map.put(FILE_NAME, filename);
            map.put(DURATION, String.valueOf(duration));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return map;
    }

    /**
     * 获得扩展名
     * @param multipartFile
     * @return
     */
    private String getExtOfFile(MultipartFile multipartFile) {
        // 获取文件的 名称.扩展名
        String oldName = multipartFile.getOriginalFilename();
        String extensionName = "";
        // 获取原来的扩展名
        if ((oldName != null) && (oldName.length() > 0)) {
            int dot = oldName.lastIndexOf('.');
            if ((dot > -1) && (dot < (oldName.length() - 1))) {
                extensionName = oldName.substring(dot+1);
            }
        }
        return extensionName;
    }

    /**
     * 极光推送
     * @param yjUserRoomQrcode
     * @param voicePath 呼叫文件地址
     * @param duration 呼叫声音文件时长
     */
    private void pushCallRequest(YjUserRoomQrcode yjUserRoomQrcode, String voicePath, String duration) {

        MyThreadPoolExecutor.getInstance().queueJob(() -> {
            String hallId = yjUserRoomQrcode.getMahjongHallRoom().getMahjongHall().getId();
            String openid = yjUserRoomQrcode.getOpenid();
            String roomQrcode = yjUserRoomQrcode.getRoomQrcode();

            // 保存数据
            YjRoomCalls yjRoomCalls = new YjRoomCalls();
            yjRoomCalls.setOpenid(openid);
            yjRoomCalls.setMahjongHallRoom(yjUserRoomQrcode.getMahjongHallRoom());
            yjRoomCalls.setMahjongHallRoomQrcode(roomQrcode);
            yjRoomCalls.setVoicePath(voicePath);
            yjRoomCalls.setDuration(Long.parseLong(duration));
            yjRoomCallsService.save(yjRoomCalls);

            // 极光推送

            // 获得二维码对应的棋牌室房间信息
            YjMahjongHallRoom hallRoom = yjMahjongHallRoomService.getRoomByQrcode(roomQrcode);
            String hallRoomName = hallRoom.getName(); // 棋牌室房间名称

            YjMahjongHall hall = yjMahjongHallService.get(hallId);
            String hallName = hall.getName(); // 棋牌室名称

            // 获得棋牌室的服务生列表
            List<YjMahjongHallWaiters> inWorkWaiters = yjMahjongHallWaitersService.findInWorkWaitersByHallId(hallId);
            if (inWorkWaiters == null || inWorkWaiters.isEmpty()) {
                log.info("棋牌室(id=" + hallId + ")无处于工作状态的服务生，推送中止");
                return;
            }

            // 新增呼叫-服务生关联关系 只要处于工作状态就要在呼叫请求列表内看到呼叫请求，不管是否设置了接收呼叫请求
            yjRoomCallsWaitersService.batchCreate(yjRoomCalls.getId(), inWorkWaiters);

            // 获得接收呼叫语音推送的棋牌室的服务生列表
            List<YjMahjongHallWaiters> waiters = inWorkWaiters.stream().filter(x -> new Integer(1).equals(x.getReceiveCall())).collect(Collectors.toList());
            // List<YjMahjongHallWaiters> waiters = yjMahjongHallWaitersService.findEffectiveByHallId(hallId);
            if (waiters == null || waiters.isEmpty()) {
                log.info("棋牌室(id=" + hallId + ")无接收语音推送的服务生，推送中止");
                return;
            }
            // 服务生手机号列表
            List<String> phonesList = waiters.stream().map(YjMahjongHallWaiters::getMobile).distinct().collect(Collectors.toList());

            // 极光推送
            doJPushWithRetry(phonesList, yjRoomCalls.getId(), openid, yjUserRoomQrcode.getMahjongHallRoom());
        });
    }

    /**
     * 极光推送 最多推送3次
     * @param phonesList
     * @param roomCallsId
     * @param openid
     * @param yjMahjongHallRoom
     */
    private void doJPushWithRetry(List<String> phonesList, String roomCallsId, String openid, YjMahjongHallRoom yjMahjongHallRoom) {
        //推送呼叫请求后，如果没有被响应，则过一分钟后再推送一次，如果再过一分钟还未被响应，则再推送一次。最多推送3次
        int retryTimes = 0;

        try {
            do {
                String redisKey = CALL_PUSH_REDIS_PREFIX + roomCallsId;

                if (retryTimes == 0) { //第一次循环
                    doJPush(phonesList, openid, yjMahjongHallRoom);

                    // 将推送信息存放在redis里面，key-> room_call_id, value -> retryTimes
                    //redisUtil.incrBy(redisKey, 1L);
                    redisUtil.setEx(redisKey, String.valueOf(retryTimes + 1), 4 * 60, TimeUnit.SECONDS);
                } else { //不是第一次循环，需要判断当前呼叫是否已经得到了响应
                    String redisRetryTimes = redisUtil.get(redisKey);
                    if (StringUtils.isBlank(redisRetryTimes)) { //key不存在了，说明已经被响应过了（因为在响应的时候我们把redis-key给删掉了）
                        log.info(roomCallsId + "已经被响应了，所以不再推送");
                        break;
                    }

                    // 判断当前redisRetryTimes是否比MaxRetryTimes要大
                    int iRedisRetryTimes = Integer.valueOf(redisRetryTimes);
                    if (iRedisRetryTimes >= MAX_RETRY_TIMES) {
                        log.info(roomCallsId + "已经推送了最大次数，所以不再推送");
                        break;
                    }

                    // 再次推送
                    doJPush(phonesList, openid, yjMahjongHallRoom, iRedisRetryTimes + 1);
                    redisUtil.incrBy(redisKey, 1L);
                }

                retryTimes++;
                Thread.sleep(60000); //休眠1分钟
            } while(retryTimes < MAX_RETRY_TIMES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 极光推送
     * @param phonesList 服务生手机号列表
     * @param openid openid
     * @param yjMahjongHallRoom 棋牌室房间
     */
    private void doJPush(List<String> phonesList, String openid, YjMahjongHallRoom yjMahjongHallRoom) {
        doJPush(phonesList, openid, yjMahjongHallRoom, 1);
    }

    /**
     * 极光推送
     * @param phonesList 服务生手机号列表
     * @param openid openid
     * @param yjMahjongHallRoom 棋牌室房间
     * @param callTimes 呼叫次数
     */
    private void doJPush(List<String> phonesList, String openid, YjMahjongHallRoom yjMahjongHallRoom, int callTimes) {
        // 组装消息内容
        String notification_content = "您有新的服务请求，请前往幺久app进行处理";
        String notification_title = "服务请求通知";

        JPushResponse pushResponse = JpushClientUtil.sendCallPushToWaiters(phonesList.toArray(new String[phonesList.size()]), notification_title, notification_content, callTimes);
        // 新增推送日志
        YjRoomCallsPushLog yjRoomCallsPushLog = new YjRoomCallsPushLog();
        yjRoomCallsPushLog.setOpenid(openid);
        yjRoomCallsPushLog.setMahjongHallRoom(yjMahjongHallRoom);
        yjRoomCallsPushLog.setRequestJson(pushResponse.getRequestJson());
        yjRoomCallsPushLog.setResponseJson(pushResponse.getResponseJson());
        yjRoomCallsPushLog.setResult(pushResponse.getResult());
        yjRoomCallsPushLog.setErrorMsg(pushResponse.getErrorMsg());
        yjRoomCallsPushLogService.save(yjRoomCallsPushLog);
    }

}
